home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / thread.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-11  |  24.1 KB  |  891 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     thread.c
  4.  
  5.     This module handles sorting subject windows into threads.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <ctype.h>
  13. #include <stdio.h>
  14.  
  15. #include "glob.h"
  16. #include "thread.h"
  17. #include "qsort.h"
  18. #include "newswatcher.h"
  19. #include "strutil.h"
  20. #include "memutil.h"
  21. #include "dialog.h"
  22. #include "header.h"
  23. #include "cache.h"
  24.  
  25.  
  26.  
  27. typedef struct TSortInfo {
  28.     char *canon;                /* ptr to canonical subject string */
  29.     TSubject *subject;            /* ptr to subject array element */
  30.     short index;                /* index in subject array */
  31.     long number;                /* article number */
  32.     short partNum;                /* part number, or 0x7fff if not a part */
  33.     short numParts;                /* number of parts, or 0x7fff if not a part */
  34.     long threadHeadNumber;        /* article number of first article in thread  */
  35.     short threadOrdinal;        /* article ordinal in thread (1,2,3,...) */
  36.     Boolean fromCache;            /* true if from cache */
  37.     Boolean potentialPart;        /* true if potential part */
  38. } TSortInfo, *TSortInfoPtr, **TSortInfoHandle;
  39.  
  40.  
  41.  
  42. static CStr255 gGroupName;                /* group name */
  43. static Handle gStrings;                    /* handle to subject and author strings */
  44. static Boolean gParentIsUserGroupList;    /* true if parent window is user group list */
  45.  
  46.  
  47.  
  48. /*----------------------------------------------------------------------------
  49.     IsPartTail 
  50.     
  51.     Check string for tail of part indicator.
  52.     
  53.     Entry:    x = pointer to string.
  54.             lastChar = required trailing character, or 0 if none.
  55.             
  56.     Exit:    function result = true if end of part indicator, in which case:
  57.                 *end = pointer to character following end of part indicator.
  58.                 *partNum = part number.
  59.                 *numParts = number of parts.
  60. ----------------------------------------------------------------------------*/
  61.  
  62. static Boolean IsPartTail (char *x, char lastChar, char **end, short *partNum,
  63.     short *numParts)
  64. {
  65.     short pNum, numP;
  66.  
  67.     while (isLWSP(*x)) x++;
  68.     if (!isdigit(*x)) return false;
  69.     pNum = CrackNum(&x);
  70.     while (isLWSP(*x)) x++;
  71.     if (*x == '/' || *x == '|' || *x == '\\') {
  72.         x++;
  73.     } else if (strncmp(x, "of", 2) == 0) {
  74.         x += 2;
  75.     } else {
  76.         return false;
  77.     }
  78.     while (isLWSP(*x)) x++;
  79.     if (!isdigit(*x)) return false;
  80.     numP = CrackNum(&x);
  81.     if (pNum > numP) return false;
  82.     if (lastChar != 0) {
  83.         while (isLWSP(*x)) x++;
  84.         if (*x != lastChar) return false;
  85.         x++;
  86.     }
  87.     while (isLWSP(*x)) x++;
  88.     *end = x;
  89.     *partNum = pNum;
  90.     *numParts = numP;
  91.     return true;
  92. }
  93.  
  94.  
  95.  
  96. /*----------------------------------------------------------------------------
  97.     CheckForPartIndicator 
  98.     
  99.     Check subject for part indicator.
  100.     
  101.     Entry:    p = pointer to sort info record.
  102.             
  103.     Exit:    If the subject contains a part indicator:
  104.                 p->partNum = part number.
  105.                 p->numParts = number of parts.
  106.                 p->potentialPart = true if potential part.
  107.                 part indicator substring stripped from p->canon.
  108.                 if the part indicator is preceded by non-white space, the 
  109.                     tail of the subject is also stripped from p->canon.
  110.                 number of parts appended to end of p->canon.
  111.                 
  112.     Part indicators are defined as follows, using the notation of RFC 822:
  113.  
  114.         part-indicator = "part" part-n-of-m
  115.                        / "(" part-n-of-m ")"
  116.                        / "[" part-n-of-m "]"
  117.                        / "{" part-n-of-m "}"
  118.                        / "<" part-n-of-m ">"
  119.     
  120.         part-n-of-m = number "of" number
  121.                     / number "/" number
  122.                     / number "|" number        
  123.                     / number "\" number        ; first number <= second number
  124.  
  125.         number = 1*DIGIT
  126.         
  127.     If the subject contains a part indicator, any subject prefix in the 
  128.     following format is also stripped (for the comp.binaries.ibm.* groups):
  129.  
  130.         "v" number "i" number ":"
  131.         
  132.     A "potential" part is a "part-n-of-m", e.g., a part indicator without the
  133.     word "part" in front or the brackets. A "potential" part is special cased.
  134.     It is considered to be a part if and only if some other matching part is
  135.     also present in the subject list.
  136. ----------------------------------------------------------------------------*/
  137.  
  138. static void CheckForPartIndicator (TSortInfoPtr p)
  139. {
  140.     char *x, *end, *y;
  141.     short partNum, numParts;
  142.     Boolean potentialPart = false;
  143.     
  144.     x = p->canon;
  145.     while (true) {
  146.         x = strpbrk(x, "([{<p0123456789");
  147.         if (x == nil) return;
  148.         if (*x == '(') {
  149.             if (IsPartTail(x+1, ')', &end, &partNum, &numParts)) break;
  150.         } else if (*x == '[') {
  151.             if (IsPartTail(x+1, ']', &end, &partNum, &numParts)) break;
  152.         } else if (*x == '{') {
  153.             if (IsPartTail(x+1, '}', &end, &partNum, &numParts)) break;
  154.         } else if (*x == '<') {
  155.             if (IsPartTail(x+1, '>', &end, &partNum, &numParts)) break;
  156.         } else if (*x == 'p' && strncmp(x, "part", 4) == 0)  {
  157.             if (IsPartTail(x+4, 0, &end, &partNum, &numParts)) break;
  158.         } else {
  159.             if (IsPartTail(x, 0, &end, &partNum, &numParts)) {
  160.                 potentialPart = true;
  161.                 break;
  162.             }
  163.         }
  164.         x++;
  165.     }
  166.     p->partNum = partNum;
  167.     p->numParts = numParts;
  168.     p->potentialPart = potentialPart;
  169.     y = p->canon;
  170.     while (*y >= 0 && !isalnum(*y)) y++;
  171.     if (x == y) {
  172.         BlockMoveData(end, x, strlen(end)+1);
  173.     } else {
  174.         *x = 0;
  175.     }
  176.     x = p->canon;
  177.     if (*x == 'v') {
  178.         x++;
  179.         if (isdigit(*x)) {
  180.             x++;
  181.             while (isdigit(*x)) x++;
  182.             if (*x == 'i') {
  183.                 x++;
  184.                 if (isdigit(*x)) {
  185.                     x++;
  186.                     while (isdigit(*x)) x++;
  187.                     if (*x == ':') {
  188.                         x++;
  189.                         while (isLWSP(*x)) x++;
  190.                         BlockMoveData(x, p->canon, strlen(x)+1);
  191.                     }
  192.                 }
  193.             }
  194.         }
  195.     }
  196.     x = p->canon;
  197.     while (*x != 0) x++;
  198.     sprintf(x, "%d", numParts);
  199. }
  200.  
  201.  
  202.  
  203. /*----------------------------------------------------------------------------
  204.     InitSortInfo 
  205.     
  206.     Initialize sorting info data structures.
  207.     
  208.     Entry:    subjectArray = handle to subject array.
  209.             numSubjects = number of subjects.
  210.             strings = handle to subject strings.
  211.             
  212.     Exit:    function result = error code.
  213.             *sortInfo = handle to locked sort info array.
  214.             *canonicalStrings = handle to locked canonical subject strings.
  215.             *sortInfoPtrs = handle to locked array of pointers into the 
  216.                 sort info array.
  217.             subject array locked.
  218. ----------------------------------------------------------------------------*/
  219.  
  220. static OSErr InitSortInfo (TSubject **subjectArray, short numSubjects, Handle strings,
  221.     TSortInfoHandle *sortInfo, Handle *canonicalStrings, TSortInfoPtr ***sortInfoPtrs)
  222. {
  223.     TSortInfoHandle sInfo = nil;
  224.     Handle cStrings = nil;
  225.     TSortInfoPtr **sInfoPtrs = nil;
  226.     OSErr err = noErr;
  227.     TSortInfoPtr p;
  228.     TSubject *q;
  229.     TSortInfoPtr *r;
  230.     short i;
  231.     char *canon, *x;
  232.     long len, reLen;
  233.     
  234.     /* Allocate memory. */
  235.     
  236.     err = MyNewHandle(numSubjects * sizeof(TSortInfo), &sInfo);
  237.     if (err != noErr) goto exit;
  238.     err = MyNewHandle(MyGetHandleSize(strings), &cStrings);
  239.     if (err != noErr) goto exit;
  240.     err = MyNewHandle(numSubjects * sizeof(TSortInfoPtr), &sInfoPtrs);
  241.     if (err != noErr) goto exit;
  242.     
  243.     /* Lock everything. */
  244.     
  245.     MyHLock(sInfo);
  246.     MyHLock(cStrings);
  247.     MyHLock(sInfoPtrs);
  248.     MyHLock(subjectArray);
  249.     
  250.     for (i = 0, p = *sInfo, q = *subjectArray, r = *sInfoPtrs, canon = *cStrings;
  251.         i < numSubjects;
  252.         i++, p++, q++, r++)
  253.     {
  254.         p->canon = canon;
  255.         p->subject = q;
  256.         p->index = i;
  257.         p->number = q->number;
  258.         p->partNum = p->numParts = 0x7fff;
  259.         p->fromCache = q->read;
  260.         p->potentialPart = false;
  261.         *r = p;
  262.         x = *strings + q->subjectOffset;
  263.         len = strlen(x);
  264.         reLen = ParseRe(x, len);
  265.         x += reLen;
  266.         while (*x != 0) {
  267.             if (isalnum(*x) || *x < 0) {
  268.                 *canon++ = tolower(*x++);
  269.             } else if (*x == '[' || *x == ']' || *x == '(' || *x == ')' ||
  270.                 *x == '/' || *x == '|' || *x == ':') {
  271.                 *canon++ = *x++;
  272.             } else {
  273.                 x++;
  274.             }
  275.         }
  276.         *canon++ = 0;
  277.         CheckForPartIndicator(p);
  278.         if (reLen > 0) {
  279.             p->partNum = p->numParts = 0x7fff;
  280.             p->potentialPart = false;
  281.         }
  282.     }
  283.     
  284.     *sortInfo = sInfo;
  285.     *canonicalStrings = cStrings;
  286.     *sortInfoPtrs = sInfoPtrs;
  287.     return noErr;
  288.     
  289. exit:
  290.  
  291.     MyDisposeHandle(sInfo);
  292.     MyDisposeHandle(cStrings);
  293.     MyDisposeHandle(sInfoPtrs);
  294.     return err;
  295. }
  296.  
  297.  
  298.  
  299. /*----------------------------------------------------------------------------
  300.     SortSubjectArrayCompare1 
  301.     
  302.     This is the comparison function used to sort the array of pointers to 
  303.     TSortInfo records into increasing order by canonical subject.
  304.     
  305.     Entry:    p = pointer to pointer to TSortInfo record.
  306.             q = pointer to pointer to TSortInfo record.
  307.             
  308.     Exit:    function result = error code.
  309.             *result
  310.                 < 0 if first subject < second subject.
  311.                 = 0 if first subject == second subject.
  312.                 > 0 if first subject > second subject.
  313.                 
  314.     The primary sort key is the canonical subject.
  315.     
  316.     The secondary sort key is the part number.
  317.     
  318.     The tertiary sort key is the article number.
  319. ----------------------------------------------------------------------------*/
  320.  
  321. static OSErr SortSubjectArrayCompare1 (TSortInfo **p, TSortInfo **q, short *result)
  322. {
  323.     OSErr err;
  324.     static short counter = 0;
  325.     short r;
  326.  
  327.     if ((++counter & 0x1f) == 0) {
  328.         err = GiveTime(false);
  329.         if (err != noErr) return err;
  330.         counter = 0;
  331.     }
  332.     
  333.     r = strcmp((**p).canon, (**q).canon);
  334.     if (r != 0 ) {
  335.         *result = r;
  336.     } else {
  337.         r = (**p).partNum - (**q).partNum;
  338.         if (r != 0) {
  339.             *result = r;
  340.         } else {
  341.             *result = (**p).number < (**q).number ? -1 : +1;
  342.         }
  343.     }
  344.     return noErr;
  345. }
  346.  
  347.  
  348.  
  349. /*----------------------------------------------------------------------------
  350.     SortSubjectArrayCompare2 
  351.     
  352.     This is the comparison function used to sort the array of pointers to 
  353.     TSortInfo records into final thread order.
  354.     
  355.     Entry:    p = pointer to pointer to TSortInfo record.
  356.             q = pointer to pointer to TSortInfo record.
  357.             
  358.     Exit:    function result = error code.
  359.             *result
  360.                 < 0 if first subject < second subject.
  361.                 = 0 if first subject == second subject.
  362.                 > 0 if first subject > second subject.
  363.                 
  364.     The primary sort key is the thread head article number.
  365.     
  366.     The secondary sort key is the thread ordinal.
  367. ----------------------------------------------------------------------------*/
  368.  
  369. static OSErr SortSubjectArrayCompare2 (TSortInfo **p, TSortInfo **q, short *result)
  370. {
  371.     OSErr err;
  372.     static short counter = 0;
  373.  
  374.     if ((++counter & 0x1f) == 0) {
  375.         err = GiveTime(false);
  376.         if (err != noErr) return err;
  377.         counter = 0;
  378.     }
  379.     
  380.     if ((**p).threadHeadNumber == (**q).threadHeadNumber) {
  381.         *result = (**p).threadOrdinal - (**q).threadOrdinal;
  382.     } else {
  383.         *result = (**p).threadHeadNumber < (**q).threadHeadNumber ? -1 : +1;
  384.     }
  385.     return noErr;    
  386. }
  387.  
  388.  
  389.  
  390. /*----------------------------------------------------------------------------
  391.     ProcessThread 
  392.     
  393.     Process a thread.
  394.     
  395.     Entry:    threadHeadPtr = pointer to first element of TSortInfo array
  396.                 for thread.
  397.             threadEndPtr = pointer to element following last element of
  398.                 TSortInfo array for thread.
  399.             
  400.     Exit:    function result = error code.
  401.             
  402.     On entry, the "fromCache" field of each TSortInfo record is true if the 
  403.     article came from the parts cache, false if the article was read
  404.     from the net.
  405.     
  406.     On exit, the following fields are set in each TSubject record:
  407.  
  408.         threadHeadIndex = index in subject array of thread head.
  409.         threadOrdinal = article ordinal within thread (1,2,3,...).    
  410.         threadLength = length of thread.
  411.         nextInThread = index in subject array of next article in thread,
  412.             or -1 if last article in thread. 
  413.         partNum = part number, or 0x7fff if not a part.
  414.         numParts = number of parts, or 0x7fff if not a part.
  415.         read = false
  416.         incomplete = true if article is part of an incomplete multiple
  417.             part thread.        
  418.         inList = true if article should be included in list displayed
  419.             to user (thread contains at least one unread article).
  420.             
  421.     On exit, the following fields are set in each TSortInfo record:
  422.         
  423.         threadHeadNumber = article number of first article in thread.
  424.         threadOrdinal = article ordinal within thread (1,2,3,...).
  425.         
  426.     On exit, the following fields in the TSortInfo records may be
  427.     adjusted:
  428.     
  429.         partNum = part number, or 0x7fff if not a part.
  430.         numParts = number of parts, 0r 0x7fff if not a part.
  431. ----------------------------------------------------------------------------*/
  432.  
  433. static OSErr ProcessThread (TSortInfo **threadHeadPtr, TSortInfo **threadEndPtr)
  434. {
  435.     TSortInfo **s, *x;
  436.     TSubject *q, *prevInList;
  437.     short threadLength, threadHeadNumber, threadHeadIndex, threadOrdinal;
  438.     short numParts, lastPart, numArtsWhichAreParts;
  439.     Boolean inList, incomplete, haveNonPotentialPart, cacheParts;
  440.     Boolean haveNewPart, complete, haveOldPart;
  441.     CStr255 subject, author;
  442.     OSErr err = noErr;
  443.     
  444.     numParts = 0;
  445.     haveNewPart = false;
  446.     haveOldPart = false;
  447.     numArtsWhichAreParts = 0;
  448.     haveNonPotentialPart = false;
  449.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  450.         x = *s;
  451.         if (x->fromCache && x->numParts < 0x7fff) haveOldPart = true;
  452.         if (!x->fromCache && x->numParts < 0x7fff) haveNewPart = true;
  453.         if (x->numParts < 0x7fff) {
  454.             if (x->numParts > numParts) numParts = x->numParts;
  455.             numArtsWhichAreParts++;
  456.             if (!x->potentialPart) haveNonPotentialPart = true;
  457.         }
  458.     }
  459.     
  460.     if (numArtsWhichAreParts == 0) {
  461.         incomplete = false;
  462.         cacheParts = false;
  463.         complete = false;
  464.     } else if (numArtsWhichAreParts == 1 && !haveNonPotentialPart) {
  465.         incomplete = false;
  466.         cacheParts = true;
  467.         complete = false;
  468.     } else {
  469.         cacheParts = true;
  470.         lastPart = 0;
  471.         for (s = threadHeadPtr; s < threadEndPtr; s++) {
  472.             x = *s;
  473.             if (x->partNum == lastPart+1) lastPart = x->partNum;
  474.         }
  475.         cacheParts = incomplete = lastPart < numParts;
  476.         complete = !incomplete && haveOldPart;
  477.     }
  478.     
  479.     threadHeadNumber = (**threadHeadPtr).number;
  480.     threadOrdinal = 1;
  481.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  482.         x = *s;
  483.         x->threadHeadNumber = threadHeadNumber;
  484.         x->threadOrdinal = threadOrdinal;
  485.         q = x->subject;
  486.         inList = !x->fromCache || 
  487.             (!incomplete && haveNewPart && x->numParts < 0x7fff);
  488.         if (inList) {
  489.             if (threadOrdinal == 1) threadHeadIndex = x->index;
  490.             if (cacheParts && !x->fromCache && x->partNum < 0x7fff && 
  491.                 gParentIsUserGroupList) 
  492.             {
  493.                 /* This is a new part in an incomplete thread - 
  494.                    add it to the cache */
  495.                 strcpy(subject, *gStrings + q->subjectOffset);
  496.                 strcpy(author, *gStrings + q->authorOffset);
  497.                 err = AddCachedArticle(gGroupName, q->number, subject, author);
  498.                 if (err != noErr) return err;
  499.             } else if (!incomplete && x->fromCache && x->partNum < 0x7fff &&
  500.                 gParentIsUserGroupList) 
  501.             {
  502.                 /* This is part of a thread which is now
  503.                    complete - remove it from the cache */
  504.                 err = DeleteCachedArticle(gGroupName, q->number);
  505.                 if (err != noErr) return err;
  506.             }
  507.             q->threadHeadIndex = threadHeadIndex;
  508.             q->threadOrdinal = threadOrdinal;
  509.             if (numArtsWhichAreParts == 1 && x->potentialPart)
  510.                 x->partNum = x->numParts = 0x7fff;
  511.             q->partNum = x->partNum;
  512.             q->numParts = x->numParts;
  513.             q->read = false;
  514.             q->incomplete = incomplete;
  515.             q->complete = complete;
  516.             q->inList = true;
  517.             threadOrdinal++;
  518.         } else {
  519.             q->inList = false;
  520.         }
  521.     }
  522.     
  523.     threadLength = threadOrdinal - 1;
  524.     if (threadLength == 0) return noErr;
  525.     
  526.     prevInList = nil;
  527.     for (s = threadHeadPtr; s < threadEndPtr; s++) {
  528.         x = *s;
  529.         q = x->subject;
  530.         if (q->inList) {
  531.             q->threadLength = threadLength;
  532.             if (prevInList != nil) prevInList->nextInThread = x->index;
  533.             prevInList = q;
  534.         }
  535.     }
  536.     prevInList->nextInThread = -1;
  537.     
  538.     return noErr;
  539. }
  540.  
  541.  
  542.  
  543. /*----------------------------------------------------------------------------
  544.     SortArray 
  545.     
  546.     Sort the array of TSortInfo pointers into thread order. 
  547.     
  548.     Entry:    sortInfoPtrs = pointer to array of pointers to TSortInfo records.
  549.             numSubjects = number of subjects.
  550.             
  551.     Exit:    function result = error code. 
  552. ----------------------------------------------------------------------------*/
  553.  
  554. static OSErr SortArray (TSortInfoPtr *sortInfoPtrs, short numSubjects)
  555. {
  556.     TSortInfo **r, **threadHeadPtr;
  557.     char *threadHeadSubject;
  558.     Boolean newThread;
  559.     short i;
  560.     OSErr err = noErr;
  561.     
  562.     /*    Sort the pointer array into increasing order by canonical subject. 
  563.         This brings threads together, although the threads are not in the 
  564.         right order yet. The article numbers are used as a secondary sort 
  565.         key to kep the articles within a thread in the correct order. */
  566.         
  567.     err = FastQSort(sortInfoPtrs, numSubjects, sizeof(TSortInfoPtr),
  568.         (SortCmpFunction)SortSubjectArrayCompare1);
  569.     if (err != noErr) return err;
  570.     
  571.     /*    Process the threads. */
  572.         
  573.     for (i = 0, r = sortInfoPtrs; i < numSubjects; i++, r++) {
  574.         newThread = i == 0 || strcmp((**r).canon, threadHeadSubject) != 0;
  575.         if (newThread) {
  576.             if (i != 0) {
  577.                 err = ProcessThread(threadHeadPtr, r);
  578.                 if (err != noErr) return err;
  579.             }
  580.             threadHeadPtr = r;
  581.             threadHeadSubject = (**r).canon;
  582.         }
  583.     }
  584.     err = ProcessThread(threadHeadPtr, r);
  585.     if (err != noErr) return err;
  586.     
  587.     /*    Sort the pointer array into final thread order. The primary
  588.         sort key is threadHeadNumber. The secondary sort key is the
  589.         thread ordinal. This final sort sorts the threads into proper 
  590.         chronological order, keeping the articles within the threads 
  591.         together. */
  592.         
  593.     return FastQSort(sortInfoPtrs, numSubjects, sizeof(TSortInfoPtr),
  594.         (SortCmpFunction)SortSubjectArrayCompare2);
  595. }
  596.  
  597.  
  598.  
  599. /*----------------------------------------------------------------------------
  600.     SplitPartThreads
  601.     
  602.     Split part threads into two threads, the first one containing just the
  603.     parts, and the second one containing just the followups.
  604.     
  605.     Entry:    subjectArray = pointer to subject array
  606.             numSubjects = number of subjects.
  607. ----------------------------------------------------------------------------*/
  608.  
  609. static void SplitPartThreads (TSubject *subjectArray, short numSubjects)
  610. {
  611.     short i, j;
  612.     TSubject *s, *t;
  613.     short partsThreadLength, followupsThreadLength;
  614.     short followupsThreadHeadIndex;
  615.     short nextInThread;
  616.         
  617.     for (i = 0, s = subjectArray; i < numSubjects; i++, s++) {
  618.         if (!s->inList) continue;
  619.         if (s->threadOrdinal != 1) continue;
  620.         if (s->threadLength == 1) continue;
  621.         if (s->partNum == 0x7fff) continue;
  622.         t = s;
  623.         partsThreadLength = 0;
  624.         while (t->partNum < 0x7fff && t->nextInThread != -1) {
  625.             partsThreadLength++;
  626.             t = subjectArray + t->nextInThread;
  627.         }
  628.         if (t->partNum < 0x7fff) continue;
  629.         t = s;
  630.         for (j = 1; j <= partsThreadLength; j++) {
  631.             t->threadLength = partsThreadLength;
  632.             nextInThread = t->nextInThread;
  633.             if (j == partsThreadLength) {
  634.                 followupsThreadHeadIndex = nextInThread;
  635.                 t->nextInThread = -1;
  636.             }
  637.             t = subjectArray + nextInThread;
  638.         }
  639.         followupsThreadLength = t->threadLength - partsThreadLength;
  640.         for (j = 1; j <= followupsThreadLength; j++) {
  641.             t->threadHeadIndex = followupsThreadHeadIndex;
  642.             t->threadOrdinal = j;
  643.             t->threadLength = followupsThreadLength;
  644.             t->incomplete = t->complete = false;
  645.             t = subjectArray + t->nextInThread;
  646.         }
  647.     }
  648. }
  649.  
  650.  
  651.  
  652. /*----------------------------------------------------------------------------
  653.     SortSubjectArrayCompare3
  654.     
  655.     This is the comparison function used to sort an array of pointers to 
  656.     TSubject records into increasing order by article number. It is used
  657.     by the BuildSortArrayByNumber function below.
  658.     
  659.     Entry:    p = pointer to pointer to TSubject record.
  660.             q = pointer to pointer to TSubject record.
  661.             
  662.     Exit:    function result = error code.
  663.             *result
  664.                 < 0 if first article number < second article number.
  665.                 > 0 if first article number > second article number.
  666. ----------------------------------------------------------------------------*/
  667.  
  668. static OSErr SortSubjectArrayCompare3 (TSubject **p, TSubject **q, short *result)
  669. {
  670.     OSErr err;
  671.     static short counter = 0;
  672.  
  673.     if ((++counter & 0x1f) == 0) {
  674.         err = GiveTime(false);
  675.         if (err != noErr) return err;
  676.         counter = 0;
  677.     }
  678.     
  679.     *result = (**p).number < (**q).number ? -1 : +1;
  680.     return noErr;
  681. }
  682.  
  683.  
  684.  
  685. /*----------------------------------------------------------------------------
  686.     BuildSortByNumberArray 
  687.     
  688.     Build the SortByNumber array for the subject window.
  689.     
  690.     Entry:    wind = pointer to window record.
  691.             
  692.     Exit:    function result = error code.
  693.             (**info).sortByNumber = handle to array of offsets to elements of
  694.                 subject array which are in the list, sorted by article
  695.                 number. 
  696. ----------------------------------------------------------------------------*/
  697.  
  698. static OSErr BuildSortByNumberArray (WindowPtr wind)
  699. {
  700.     TWindow **info;
  701.     TSubject **subjectArray;
  702.     short numSubjects, numSubjectsInList;
  703.     long **sortByNumber = nil;
  704.     OSErr err = noErr;
  705.     short i;
  706.     TSubject *p;
  707.     long *q;
  708.     char state;
  709.     
  710.     info = (TWindow**)GetWRefCon(wind);
  711.     subjectArray = (**info).subjectArray;
  712.     state = MyHGetState(subjectArray);
  713.     numSubjects = (**info).numSubjects;
  714.     numSubjectsInList = (**info).numSubjectsInList;
  715.     
  716.     /* Allocate the array. */
  717.     
  718.     err = MyNewHandle(numSubjectsInList*sizeof(long), &sortByNumber);
  719.     if (err != noErr) goto exit;
  720.     
  721.     /* Initialize the array. During the sort, the array elements are pointers
  722.        into the locked subject array, rather than offsets. */
  723.     
  724.     MyHLock(sortByNumber);
  725.     MyHLock(subjectArray);
  726.  
  727.     for (i = 0, p = *subjectArray, q = *sortByNumber;
  728.         i < numSubjects;
  729.         i++, p++)
  730.     {
  731.         if (p->inList) *q++ = (long)p;
  732.     }
  733.     
  734.     /* Sort the array into increasing order by article number. */
  735.  
  736.     err = FastQSort(*sortByNumber, numSubjectsInList, sizeof(long),
  737.         (SortCmpFunction)SortSubjectArrayCompare3);
  738.     if (err != noErr) goto exit;
  739.     
  740.     /* Convert the array from pointers to offsets. */
  741.     
  742.     for (i = 0, q = *sortByNumber; i < numSubjectsInList; i++, q++)
  743.         *q = (char*)*q - (char*)*subjectArray;
  744.     
  745.     MyHUnlock(sortByNumber);
  746.     MyHSetState(subjectArray, state);
  747.         
  748.     (**info).sortByNumber = sortByNumber;
  749.     return noErr;
  750.     
  751. exit:
  752.  
  753.     MyDisposeHandle(sortByNumber);
  754.     MyHSetState(subjectArray, state);
  755.     return err;
  756. }
  757.  
  758.  
  759.  
  760. /*----------------------------------------------------------------------------
  761.     BuildThreads 
  762.     
  763.     Build subject window threads.
  764.     
  765.     Entry:    wind = pointer to subject window.
  766.     
  767.     Exit:    function result = error code.
  768. ----------------------------------------------------------------------------*/
  769.  
  770. OSErr BuildThreads (WindowPtr wind)
  771. {
  772.     TWindow **info, **parentInfo;
  773.     WindowPtr parent;
  774.     TSubject **subjectArray;
  775.     short numSubjects, numSubjectsInList;
  776.     ListHandle theList;
  777.     TSortInfoHandle sortInfo = nil;
  778.     TSortInfoPtr **sortInfoPtrs = nil;
  779.     Handle canonicalStrings = nil;
  780.     TSubject *q;
  781.     TSortInfoPtr *r;
  782.     short *pCells;
  783.     short *pCellArray;
  784.     short offset;
  785.     short numCells;
  786.     short i;
  787.     OSErr err = noErr;
  788.     char state;
  789.     
  790.     /* Initialize. */
  791.     
  792.     info = (TWindow**)GetWRefCon(wind);
  793.     subjectArray = (**info).subjectArray;
  794.     state = MyHGetState(subjectArray);
  795.     gStrings = (**info).strings;
  796.     theList = (**info).theList;
  797.     strcpy(gGroupName, *gGroupNames + (**info).groupNameOffset);
  798.     parent = (**info).parentWindow;
  799.     if (parent == nil) {
  800.         gParentIsUserGroupList = false;
  801.     } else {
  802.         parentInfo = (TWindow**)GetWRefCon(parent);
  803.         gParentIsUserGroupList = (**parentInfo).groupKind == kUserGroup;
  804.     }
  805.     
  806.     /* Append cached articles to subject array. */
  807.     
  808.     if (gParentIsUserGroupList) {
  809.         err = AppendCachedArticles(wind);
  810.         if (err != noErr) goto exit;
  811.     }
  812.     numSubjects = (**info).numSubjects;
  813.  
  814.     /* Initialize the sort info data structures. */
  815.  
  816.     err = InitSortInfo(subjectArray, numSubjects, gStrings,
  817.         &sortInfo, &canonicalStrings, &sortInfoPtrs);
  818.     if (err != noErr) goto exit;
  819.  
  820.     /* Sort the sortInfoPtrs array into thread order. */
  821.     
  822.     err = SortArray(*sortInfoPtrs, numSubjects);
  823.     if (err != noErr) goto exit;
  824.     
  825.     /* We can and should get rid of the canonicalStrings memory block at this point. */
  826.     
  827.     MyDisposeHandle(canonicalStrings);
  828.     canonicalStrings = nil;
  829.     
  830.     /* Split part threads. */
  831.     
  832.     SplitPartThreads(*subjectArray, numSubjects);
  833.     
  834.     /* Compute the number of subjects in the list and the number
  835.        of cells in the list. */
  836.     
  837.     numSubjectsInList = numSubjects;
  838.     numCells = numSubjects;
  839.     for (i = 0, q = *subjectArray; i < numSubjects; i++, q++) {
  840.         if (!q->inList) {
  841.             numSubjectsInList--;
  842.             numCells--;
  843.         } else if (q->collapsed && q->threadOrdinal != 1) {
  844.             numCells--;
  845.         }
  846.     }
  847.     (**info).numSubjectsInList = numSubjectsInList;
  848.  
  849.     /* Create the List Manager cell list. */
  850.     
  851.     LSetDrawingMode(false, theList);
  852.     LAddRow(numCells, 0, theList);
  853.     err = MySetHandleSize((**theList).cells, 2*numCells);
  854.     if (err != noErr) goto exit;
  855.     pCells = (short*)*((**theList).cells);
  856.     pCellArray = (**theList).cellArray;
  857.     offset = 0;
  858.     for (i = 0, r = *sortInfoPtrs; i < numSubjects; i++, r++) {
  859.         q = (**r).subject;
  860.         if (q->inList) {
  861.             if (!q->collapsed || q->threadOrdinal == 1) {
  862.                 *pCellArray++ = offset;
  863.                 *pCells++ = (*r)->index;
  864.                 offset += 2;
  865.             }
  866.         }
  867.     }
  868.     *pCellArray = offset;
  869.     LSetDrawingMode(true, theList);
  870.     
  871.     /* Dispose the sortInfo and sortInfoPtrs memory blocks. */
  872.         
  873.     MyHSetState(subjectArray, state);
  874.     MyDisposeHandle(sortInfo);
  875.     MyDisposeHandle(sortInfoPtrs);
  876.     
  877.     /* Build the sort by number array. */
  878.     
  879.     return BuildSortByNumberArray(wind);
  880.     
  881. exit:
  882.  
  883.     MyHSetState(subjectArray, state);
  884.     MyDisposeHandle(sortInfo);
  885.     MyDisposeHandle(canonicalStrings);
  886.     MyDisposeHandle(sortInfoPtrs);
  887.     LDelRow(0, 0, theList);
  888.     LSetDrawingMode(true, theList);
  889.     return err;
  890. }
  891.